package com.itdct.onflow.generator.core

import cn.hutool.core.io.FileUtil
import cn.hutool.core.lang.UUID
import com.baomidou.mybatisplus.annotation.TableField
import com.baomidou.mybatisplus.annotation.TableId
import com.baomidou.mybatisplus.annotation.TableLogic
import com.baomidou.mybatisplus.annotation.TableName
import com.itdct.onflow.core.annotation.Column
import com.itdct.onflow.core.annotation.ColumnRelateDict
import com.itdct.onflow.core.annotation.ColumnRelateId
import com.itdct.onflow.core.annotation.TableDescription
import com.itdct.onflow.core.entity.BaseEntity
import com.itdct.onflow.core.entity.IdEntity
import com.itdct.onflow.core.extend.logError
import com.itdct.onflow.core.extend.logInfo
import com.itdct.onflow.core.extend.toUnderlineCase
import com.itdct.onflow.generator.config.GeneratorConfig
import com.itdct.onflow.generator.constant.GroupEnum
import com.itdct.onflow.generator.model.AnnotationModel
import com.itdct.onflow.generator.model.ClassModel
import com.itdct.onflow.generator.model.FieldModel
import com.itdct.onflow.generator.model.IndexModel
import java.io.File
import java.lang.reflect.Field
import java.math.BigDecimal
import java.nio.charset.Charset
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlin.reflect.KClass

/**
 * @author Zhouwx
 * @date 2025/8/4 18:18:01
 * @version 1.0
 * @description
 */
class EntityAnalyse<T : IdEntity>(
    val generatorConfig: GeneratorConfig
) {

    fun analyseClass(clazz: Class<T>): ClassModel {
        val classModel = ClassModel()
        initClassModel(classModel, clazz)

        doAnalyseClass(clazz, classModel)

        classModel.fieldList.sortBy { fieldModel -> fieldModel.sort }
        return classModel
    }

    private fun doAnalyseClass(clazz: Class<*>, classModel: ClassModel) {
        scanFields(clazz, classModel)

        val superclass = clazz.superclass
        if (superclass != null && superclass != Any::class.java) {
            doAnalyseClass(superclass, classModel)
        }
    }

    private fun scanFields(clazz: Class<*>, classModel: ClassModel) {
        for (field in clazz.declaredFields) {
            val column = field.getAnnotation(Column::class.java)
            if (column == null) {
                // INFO: Zhouwx: 2025/8/10 没有column注解的不解析
                continue
            }

            val tableField = field.getAnnotation(TableField::class.java)
            if (tableField != null && !tableField.exist) {
                // INFO: Zhouwx: 2025/8/10 忽略库中不存在的字段
                continue
            }

            val fieldModel = FieldModel()
            try {
                initFieldModel(field, classModel, fieldModel)
            } catch (e: Exception) {
                logError(e)
            }
        }
    }

    private fun initFieldModel(
        field: Field,
        classModel: ClassModel,
        fieldModel: FieldModel
    ) {
        val name = field.name
        val type = field.type
        classModel.fieldList.add(fieldModel)
        fieldModel.name = name
        fieldModel.typeName = type.simpleName.substring(0, 1).uppercase() + type.simpleName.substring(1)
        fieldModel.clazz = type
        fieldModel.className = type.name
        fieldModel.kClass = field.type.kotlin
        fieldModel.nameBigCamel = name.substring(0, 1).uppercase() + name.substring(1)
        val column = field.getAnnotation(Column::class.java)
        fieldModel.comment = column.comment
        if (column.length != 0) {
            fieldModel.length = column.length
        } else {
            when (fieldModel.kClass) {
                Int::class -> fieldModel.length = 11
                Long::class -> fieldModel.length = 20
                Double::class -> fieldModel.length = 11
                Float::class -> fieldModel.length = 6
                BigDecimal::class -> fieldModel.length = 18
                LocalDateTime::class -> fieldModel.length = 1
                LocalDate::class -> fieldModel.length = 1
                Date::class -> fieldModel.length = 1
                else -> fieldModel.length = 64
            }
        }
        fieldModel.nullable = column.nullable
        fieldModel.autoincrement = column.autoincrement
        fieldModel.defaultValue = column.defaultValue
        fieldModel.forSelect = column.forSelect
        fieldModel.sortable = column.sortable

        if (fieldModel.clazz == BigDecimal::class.java) {
            if (column.decimal != 0) {
                fieldModel.decimal = column.decimal
            } else {
                fieldModel.decimal = 2
            }
        }
        fieldModel.dbType = clazzToDbType(fieldModel.kClass!!, fieldModel.length)
        fieldModel.sort = column.sort
        fieldModel.virtual = column.virtual
        fieldModel.columnName = getFieldColumnName(field)

        val tableLogic = field.getAnnotation(TableLogic::class.java)
        if (tableLogic != null) {
            classModel.haveLogicDelete = true
            classModel.logicDeleteFieldName = fieldModel.columnName
        }

        if (column.indexed) {
            fieldModel.indexed = true
            val indexModel = IndexModel()
            classModel.indexList.add(indexModel)
            indexModel.columnName = fieldModel.columnName
            indexModel.indexName = "${classModel.tableName}_${fieldModel.columnName}_idx"
        }

        if (column.unique) {
            fieldModel.unique = true
            val indexModel = IndexModel()
            classModel.uniqueList.add(indexModel)
            indexModel.columnName = fieldModel.columnName
            indexModel.indexName = "${classModel.tableName}_${fieldModel.columnName}_uk"
        }

        if (column.primaryKey) {
            fieldModel.primaryKey = true
            val indexModel = IndexModel()
            classModel.primaryKeyList.add(indexModel)
            indexModel.columnName = fieldModel.columnName
            indexModel.indexName = "${classModel.tableName}_${fieldModel.columnName}_pk"
        }

        for (declaredAnnotation in field.declaredAnnotations) {
            val annotationClass = declaredAnnotation.annotationClass
            val annotationModel = AnnotationModel()
            fieldModel.annotationList.add(annotationModel)
            annotationModel.className = annotationClass.qualifiedName ?: ""
        }

        if (fieldModel.clazz == String::class.java) {
            if (column.collate.isNotBlank()) {
                fieldModel.collate = column.collate
            } else {
                fieldModel.collate = generatorConfig.defaultCollate
            }

            if (column.charset.isNotBlank()) {
                fieldModel.charset = column.charset
            } else {
                fieldModel.charset = generatorConfig.defaultCharset
            }
        }

        column.relateIndex.forEach {
            fieldModel.relateIndex.add(it)
        }

        column.relateUnique.forEach {
            fieldModel.relateUnique.add(it)
        }

        // INFO: Zhouwx: 2025/8/11 字典注解与id注解
        val columnRelateDict = field.getAnnotation(ColumnRelateDict::class.java)
        if (columnRelateDict != null) {
            fieldModel.dictCode = columnRelateDict.dictCode
            fieldModel.dictGenSuffix = columnRelateDict.dictGenSuffix
        }

        val columnRelateId = field.getAnnotation(ColumnRelateId::class.java)
        if (columnRelateId != null) {
            fieldModel.idGenSuffix = columnRelateId.idGenSuffix
            val targetKClass = columnRelateId.targetClass
            val targetClass = targetKClass.java
            val targetClassTableDescription = targetClass.getAnnotation(TableDescription::class.java) ?: throw RuntimeException("${targetClass.simpleName} 没有配置 @TableDescription")
            val targetTableNameAnnotation = targetClass.getAnnotation(TableName::class.java) ?: throw RuntimeException("${targetClass.simpleName} 没有配置 @TableName")
            val targetTableName = targetTableNameAnnotation.value

            var targetTableNameAbbr = targetClassTableDescription.tableNameAbbr
            if (targetTableNameAbbr.isBlank()) {
                targetTableNameAbbr = getDefaultTableAbbr(targetTableName)
            }
            if (classModel.tableNameAbbrSet.contains(targetTableNameAbbr)) {
                // INFO: Zhouwx: 2025/8/28 表名缩写列表中包含了该缩写，则需要避免表名缩写相同的情况，导致SQL语句报错
                var number = 2
                var temp = targetTableNameAbbr + number
                while (classModel.tableNameAbbrSet.contains(temp)) {
                    number++
                    temp = targetTableNameAbbr + number
                }
                targetTableNameAbbr = temp
            }

            val targetField = targetClass.getDeclaredField(columnRelateId.targetFieldName) ?: throw RuntimeException("${targetClass.simpleName} 没有找到字段 ${columnRelateId.targetFieldName}")
            fieldModel.targetColumnName = getFieldColumnName(targetField)
            fieldModel.targetClass = targetKClass
            val targetClassSimpleName = targetKClass.java.simpleName
            fieldModel.targetEntityClassName = targetClassSimpleName
            // INFO: Zhouwx: 2025/8/28 去掉结尾的Entity
            fieldModel.targetClassName = targetClassSimpleName.substring(0, targetClassSimpleName.length - 6)
            fieldModel.targetClassTableName = targetTableName
            fieldModel.targetWholeClassName = targetKClass.java.name
            fieldModel.targetFieldName = columnRelateId.targetFieldName
            fieldModel.targetTableNameAbbr = targetTableNameAbbr
        }

        // INFO: Zhouwx: 2025/8/14 主键自动加上autoIncrement
        val tableId = field.getAnnotation(TableId::class.java)
        if (tableId != null) {
            fieldModel.primaryKey = true
            fieldModel.autoincrement = true
        }

        // INFO: Zhouwx: 2025/8/25 设置vueType类型
        when (fieldModel.kClass) {
            BigDecimal::class,
            Float::class,
            Double::class,
            Long::class,
            Short::class,
            Byte::class,
            Int::class -> fieldModel.vueType = "number"

            String::class,
            LocalDateTime::class,
            LocalDate::class,
            Date::class -> fieldModel.vueType = "string"

            Boolean::class -> fieldModel.vueType = "boolean"

            List::class -> fieldModel.vueType = "Array<string>"

            else -> fieldModel.vueType = "string"
        }

        // INFO: Zhouwx: 2025/9/11 Vue页面类型
        fieldModel.vueComponentType = getVueComponentType(fieldModel)

        fieldModel.ddl = getFieldDdl(fieldModel)
    }

    private fun getVueComponentType(fieldModel: FieldModel): String {
        var vueComponentType = ""
        if (fieldModel.dictCode.isNotBlank()) {
            vueComponentType = "Select"
            return vueComponentType
        }

        if (fieldModel.targetClass != null) {
            vueComponentType = "Select"
            return vueComponentType
        }

        vueComponentType = when (fieldModel.typeName) {
            "String" -> "Input"
            "Integer" -> "InputNumber"
            "Long" -> "InputNumber"
            "Double" -> "InputNumber"
            "Float" -> "InputNumber"
            "BigDecimal" -> "InputNumber"
            "Date" -> "DatePicker"
            "LocalDateTime" -> "DatePicker"
            "LocalDate" -> "DatePicker"
            "LocalTime" -> "DatePicker"
            "Boolean" -> "Switch"
            else -> {
                "Input"
            }
        }
        return vueComponentType
    }

    private fun getFieldDdl(fieldModel: FieldModel): String {
        val notNull = if (fieldModel.nullable) "" else "NOT NULL "
        val default = if (fieldModel.defaultValue.isNotEmpty()) "DEFAULT '${fieldModel.defaultValue}' " else ""
        val comment = if (fieldModel.comment.isNotEmpty()) "COMMENT '${fieldModel.comment}' " else ""
        val collate = if (fieldModel.collate.isNotEmpty()) "COLLATE ${fieldModel.collate} " else ""
        val autoincrement = if (fieldModel.autoincrement) "AUTO_INCREMENT " else ""
        var length = getWriteLength(fieldModel)

        // INFO: Zhouwx: 2025/8/11 text类型也不显示长度
        if (fieldModel.dbType.contains("text")) {
            length = ""
        }

        if (length.isNotBlank()) {
            length = "(${length})"
        }

        return """  `${fieldModel.columnName}` ${fieldModel.dbType}${length} ${autoincrement}${collate}${notNull}${default}${comment}"""
    }

    private fun getWriteLength(fieldModel: FieldModel): String {
        when (fieldModel.kClass) {
            BigDecimal::class -> return "${fieldModel.length},${fieldModel.decimal}"
            // INFO: Zhouwx: 2025/8/11 Date类型不显示长度
            LocalDateTime::class -> return ""
            LocalDate::class -> return ""
            Date::class -> return ""
            String::class -> {
                return if (fieldModel.length > 0) {
                    fieldModel.length.toString()
                } else {
                    return "255"
                }
            }

            else -> return ""
        }
    }

    private fun clazzToDbType(clazz: KClass<*>, length: Int): String {
        var type = when (clazz) {
            Int::class -> "int"
            Long::class -> "bigint"
            Double::class -> "double"
            Float::class -> "float"
            BigDecimal::class -> "decimal"
            LocalDateTime::class -> "datetime"
            LocalDate::class -> "datetime"
            Date::class -> "datetime"
            String::class -> "varchar"
            Boolean::class -> "tinyint"
            Byte::class -> "tinyint"
            ByteArray::class -> "blob"
            Short::class -> "smallint"
            Char::class -> "char"
            else -> "varchar"
        }
        if (type == "varchar") {
            if (length > 2048 && length <= 16383) {
                type = "text"
            } else if (length > 16383) {
                type = "longtext"
            }
        }
        return type
    }

    private fun initClassModel(classModel: ClassModel, clazz: Class<out T>) {
        classModel.entityClassName = clazz.simpleName
        classModel.className = clazz.simpleName.replace("Entity", "")
        classModel.classNameLowCamel = classModel.className.substring(0, 1).lowercase() + classModel.className.substring(1)
        classModel.clazz = clazz as Class<IdEntity>
        classModel.fullClassName = clazz.name
        classModel.packageNameWithEntity = clazz.packageName
        classModel.packageName = clazz.packageName.substring(0, clazz.packageName.lastIndexOf("."))
        classModel.moduleName = clazz.name.substring(clazz.name.lastIndexOf(".") + 1)
        val tableDescription = clazz.getAnnotation(TableDescription::class.java) ?: throw RuntimeException("${clazz.name}未添加@TableDescription注解")

        classModel.classUsage = tableDescription.value
        classModel.tableEngine = tableDescription.engine
        classModel.autoIncrement = tableDescription.autoIncrement
        classModel.charset = tableDescription.charset
        classModel.collate = tableDescription.collate
        classModel.tableAutoSync = tableDescription.autoSync
        classModel.dataPermissionEnum = tableDescription.dataPermission
        val tableNameAbbr = tableDescription.tableNameAbbr

        val tableName = clazz.getAnnotation(TableName::class.java)
        if (tableName == null) {
            throw RuntimeException("${clazz.simpleName} 没有 @TableName 注解")
        }

        classModel.tableName = tableName.value
        if (tableNameAbbr.isNotBlank()) {
            classModel.tableNameAbbr = tableNameAbbr
        } else {
            classModel.tableNameAbbr = getDefaultTableAbbr(classModel.tableName)
        }
        classModel.tableNameAbbrSet.add(classModel.tableNameAbbr)
        classModel.schemaName = tableName.schema
        classModel.superClass = clazz.superclass
        classModel.superClassName = clazz.superclass.simpleName
        if (clazz.isAssignableFrom(BaseEntity::class.java)) {
            classModel.haveLogicDelete = true
            classModel.logicDeleteFieldName = generatorConfig.logicDeleteFieldName
        }

        // INFO: Zhouwx: 2025/8/13 解析uuid相关代码
        var backendPath = generatorConfig.groupModulePath[GroupEnum.BACKEND] ?: throw RuntimeException("请配置后端路径")
        backendPath = backendPath.replace("\\", "/")
        if (!backendPath.endsWith("/")) {
            backendPath += "/"
        }
        val split = backendPath.split("/")
        val moduleName = split[split.size - 2]
        val packageName = classModel.packageName
        val packagePath = packageName.replace(".", "/")
        val entityFilePath = "$backendPath${moduleName}-entity/src/main/kotlin/$packagePath/entity/${classModel.entityClassName}.kt"
        logInfo("entityFilePath is ${entityFilePath}")
        val entityFile = File(entityFilePath)
        if (!entityFile.exists()) {
            throw RuntimeException("Entity文件不存在")
        }
        classModel.entityFilePath = entityFilePath
        classModel.entityFile = entityFile

        val uuidFindString = "// [[ OnflowGenerator Entity UUID:"
        val entityFileContent = FileUtil.readString(entityFile, Charset.defaultCharset())
        if (entityFileContent.contains(uuidFindString)) {
            // INFO: Zhouwx: 2025/8/13 说明这个文件已经写入过uuid了
            val indexOf = entityFileContent.indexOf(uuidFindString)
            val uuid = entityFileContent.substring(indexOf + uuidFindString.length, entityFileContent.indexOf("]]", indexOf)).trim()
            classModel.fileUuid = uuid
            logInfo("find file uuid is $uuid")
        } else {
            val uuid = UUID.randomUUID().toString(true)
            classModel.fileUuid = uuid
        }

    }

    fun getDefaultTableAbbr(tableName: String): String {
        val tableAbbr = tableName.split("_")
            .filter {
                it.length > 1
            }.map {
                it.substring(0, 1)
            }.joinToString("")
        return tableAbbr
    }

    fun getFieldColumnName(field: Field): String {
        val tableField = field.getAnnotation(TableField::class.java)
        if (tableField != null) {
            return tableField.value
        } else {
            return field.name.toUnderlineCase()
        }
    }

}